/*
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 1, or (at your option)
** any later version.

** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.

** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include <iostream>
#include <QtCore>

#include <QApplication>
#include <QMessageBox>
#include <QByteArray>
#include <QTextCodec>
#include <QSystemTrayIcon>
#include <QMenu>

#include "ia_main_window.h"
#include "ia_settings_window.h"

IAMainWindow::IAMainWindow(QWidget * parent, Qt::WindowFlags flags)
    : QWidget(parent, flags),
#ifdef WIN32
      logoIcon(":/images/logo-win.png"),
      onlineIcon(":/images/ia_online-win.png"),
      offlineIcon(":/images/ia_offline-win.png"),
#else
      logoIcon(":/images/logo-x11.png"),
      onlineIcon(":/images/ia_online-x11.png"),
      offlineIcon(":/images/ia_offline-x11.png"),
#endif
      settings("STGSoft", "InetAccess"),
      disconnectedByUser(true),
      firstTime(true)
{
    setWindowIcon(logoIcon);
    setWindowTitle(tr("InetAccess"));
    // Layouts
    mainLayout = new QHBoxLayout(this);
    leftPanel = new QVBoxLayout();
    rightPanel = new QVBoxLayout();
    downPanel = new QGridLayout();
    // Left panel widgets
    loginLbl = new QLabel(tr("Login:"), this);
    loginFld = new QLineEdit(this);
    passwordLbl = new QLabel(tr("Password:"), this);
    passwordFld = new QLineEdit(this);
    connectBtn = new QPushButton(tr("Connect"), this);
    settingsBtn = new QPushButton(tr("Settings"), this);
    // Right panel widgets
    stateGroup = new QGroupBox(tr("State"), this);
    stateTbl = new QTableView(this);
    ind = new IAIndicator(this);
    // Down panel widgets
    freeMbLbl = new QLabel(tr("Free Mb:"), this);
    cashLbl = new QLabel(tr("Cash:"), this);
    freeMbValueLbl = new QLabel(this);
    cashValueLbl = new QLabel(this);

    createActions();

    if (QSystemTrayIcon::isSystemTrayAvailable()) {
        createTray();
    } else {
        sysTrayIcon = NULL;
    }

    // Model for table view
    statsModel = new IAStatsModel(this);
    statsModel->SetUnits(settings.value("units", "0").toInt());
    stateTbl->setModel(statsModel);

    setFixedSize(540, 240);
    setLayout(mainLayout);

    stateGroup->setLayout(rightPanel);

    mainLayout->addLayout(leftPanel);
    mainLayout->addWidget(stateGroup);

    leftPanel->addWidget(loginLbl);
    leftPanel->addWidget(loginFld);
    leftPanel->addWidget(passwordLbl);
    leftPanel->addWidget(passwordFld);
    leftPanel->addWidget(connectBtn);
    leftPanel->addWidget(settingsBtn);
    leftPanel->addStretch();

    rightPanel->addWidget(stateTbl);
    rightPanel->addWidget(ind);
    rightPanel->addLayout(downPanel);

    downPanel->addWidget(freeMbLbl, 0, 0);
    downPanel->addWidget(cashLbl, 1, 0);
    downPanel->addWidget(freeMbValueLbl, 0, 1);
    downPanel->addWidget(cashValueLbl, 1, 1);

    stateTbl->setMinimumWidth(350);
    passwordFld->setEchoMode(QLineEdit::Password);
    connectBtn->setCheckable(true);

    // "Connect" action
    connect(connectBtn, SIGNAL(toggled(bool)),
            this, SLOT(toggleConnect(bool)));
    // "Settings" action
    connect(settingsBtn, SIGNAL(clicked()),
            this, SLOT(onSettings()));

    connect(this, SIGNAL(stateChanged(bool)),
            this, SLOT(setState(bool)), Qt::QueuedConnection);
    // Action for various errors
    connect(this, SIGNAL(errorOccured(const QString &)),
            this, SLOT(onErrorOccured(const QString &)), Qt::QueuedConnection);

    connect(this, SIGNAL(incomingMessage(const QString &)),
            this, SLOT(onMessage(const QString &)), Qt::QueuedConnection);

    QString serverName = settings.value("server_name", "192.168.1.1").toString();
    int serverPort = settings.value("server_port", "5555").toInt();
    loginFld->setText(settings.value("login", "").toString());

    if (settings.value("save_password", "1").toBool()) {
        QString password(settings.value("password", "").toString());
        if (settings.value("link_password_to_hdd", "0").toBool()) {
            // Decrypt password
        }
        passwordFld->setText(password);
    }

    proto = new IA_CLIENT_PROT(serverName.toStdString(), serverPort);

    proto->SetStatusChangedCb(onStatusChanged, this);
    proto->SetStatChangedCb(onStatChanged, this);
    proto->SetInfoCb(onInfo, this);
    proto->SetErrorCb(onError, this);
    proto->SetDirNameCb(onDirNames, this);
    proto->SetReconnect(settings.value("reconnect", "0").toBool());

    if (settings.value("autoconnect", "0").toBool()) {
	onConnect();
	onHide();
    }

    if (settings.value("minimize_on_close", "1").toBool()) {
	// Do not quit on window clode
	qApp->setQuitOnLastWindowClosed(false);
    } else {
	// Normal mode
	qApp->setQuitOnLastWindowClosed(true);
    }

    proto->Start();
}

IAMainWindow::~IAMainWindow()
{
    settings.setValue("login", loginFld->text());
    if (settings.value("save_password", "1").toBool()) {
        QString password(passwordFld->text());
        if (settings.value("link_password_to_hdd", "0").toBool()) {
            // Encrypt password
        }
        settings.setValue("password", password);
    }
    proto->Stop();
    delete proto;
}

// Routines
void IAMainWindow::iaConnect()
{
    connectBtn->setText(tr("Disconnect"));
    connectBtn->setChecked(true);
    connectAction->setEnabled(false);
    disconnectAction->setEnabled(true);
}

void IAMainWindow::iaDisconnect()
{
    connectBtn->setText(tr("Connect"));
    connectBtn->setChecked(false);
    connectAction->setEnabled(true);
    disconnectAction->setEnabled(false);
}

// Slots
void IAMainWindow::onConnect()
{
    connectBtn->setText(tr("Disconnect"));
    proto->SetLogin(loginFld->text().toStdString());
    proto->SetPassword(passwordFld->text().toStdString());
    proto->Connect();
    disconnectedByUser = false;
}

void IAMainWindow::onDisconnect()
{
    connectBtn->setText(tr("Connect"));
    proto->Disconnect();
    disconnectedByUser = true;
}

// Change authorization state
void IAMainWindow::toggleConnect(bool state)
{
    if (state) {
        onConnect();
    } else {
        onDisconnect();
    }
}

void IAMainWindow::onMessage(const QString & message)
{
    QMessageBox::information(0, tr("Message"), message);
}

// Wrapper to show error messages
void IAMainWindow::onErrorOccured(const QString & errorMessage)
{
    //QMessageBox::critical(this, tr("Error"), errorMessage);
    QMessageBox::critical(0, tr("Error"), errorMessage);
}

// State changing (manually or automatically)
void IAMainWindow::changeState(bool state, const std::string & errorMessage)
{
    // We are in ia_auth_c lib's thread. Cannot draw anything!
    // Just emit detached signals
    emit stateChanged(state);

    // If error message is empty - no error
    if (errorMessage != "") {
        QByteArray msg(errorMessage.c_str());
        QTextCodec * codec = QTextCodec::codecForName("Windows-1251");
	// This method may be called async, emit signal to show error
        emit errorOccured(codec->toUnicode(msg));
    }
}

void IAMainWindow::changeStats(const LOADSTAT & stat)
{
    statsModel->SetStats(stat);
    freeMbValueLbl->setText(statsModel->getFreeMb());
    cashValueLbl->setText(statsModel->getCash());
}

// Show settings dialog
void IAMainWindow::onSettings()
{
    IASettingsWindow sw(this);

    // Setup form
    sw.setServerName(settings.value("server_name", "192.168.1.1").toString());
    sw.setServerPort(settings.value("server_port", "5555").toInt());
    sw.setSavePassword(settings.value("save_password", "1").toBool());
    sw.setLinkHDD(settings.value("link_password_to_hdd", "0").toBool());
    sw.setMinimize(settings.value("minimize_on_close", "1").toBool());
    sw.setAutoconnect(settings.value("autoconnect", "0").toBool());
    sw.setReconnect(settings.value("reconnect", "0").toBool());
    sw.setUnits(settings.value("units", "0").toInt());
    sw.setDirNames(dirNames);

    if (sw.exec() == QDialog::Accepted) {
	// Update settings
        settings.setValue("server_name", sw.getServerName());
        settings.setValue("server_port", sw.getServerPort());
        settings.setValue("save_password", sw.getSavePassword());
        settings.setValue("link_password_to_hdd", sw.getLinkHDD());
	settings.setValue("autoconnect", sw.getAutoconnect());
	settings.setValue("reconnect", sw.getReconnect());
        settings.setValue("units", sw.getUnits());

	// Change reconnection status
	proto->SetReconnect(settings.value("reconnect", "0").toBool());

	// Change networking options
	QString serverName = settings.value("server_name", "192.168.1.1").toString();
	int serverPort = settings.value("server_port", "5555").toInt();
	proto->SetServer(serverName.toStdString(), serverPort);

        statsModel->SetUnits(settings.value("units", "0").toInt());

	// Change minimization options
        if (sw.getMinimize()) {
            qApp->setQuitOnLastWindowClosed(false);
	    if (!settings.value("minimize_on_close").toBool()) {
		firstTime = true;
	    }
        } else {
            onShow();
            qApp->setQuitOnLastWindowClosed(true);
        }
	settings.setValue("minimize_on_close", sw.getMinimize());
    }
}

// For shorter calls from callbacks
void IAMainWindow::updateDirNames()
{
    statsModel->SetDirNames(dirNames, dirIndex);
}

// Callback for status changes
void IAMainWindow::onStatusChanged(int status, void * data)
{
    IAMainWindow * mainWindow = static_cast<IAMainWindow *>(data);

    std::cerr << "IAMainWindow::onStatChanged - status = " << status << std::endl;

    mainWindow->changeState(status);
}

// Callback for info :)
void IAMainWindow::onStatChanged(const LOADSTAT & stat, void * data)
{
    IAMainWindow * mainWindow = static_cast<IAMainWindow *>(data);

    mainWindow->changeStats(stat);
}

// Callback for messages :)
void IAMainWindow::onInfo(const string & message, int infoType, int showTime, int sendTime, void * data)
{
    IAMainWindow * mainWindow = static_cast<IAMainWindow *>(data);

    QByteArray msg(message.c_str());
    QTextCodec * codec = QTextCodec::codecForName("Windows-1251");
    // This method may be called async, emit signal to show error
    mainWindow->displayMessage(codec->toUnicode(msg));
}

// Callback for errors
void IAMainWindow::onError(const string & message, int netError, void * data)
{
    IAMainWindow * mainWindow = static_cast<IAMainWindow *>(data);

    //mainWindow->iaDisconnect();
    mainWindow->changeState(false, message);
}

// Callback for dir names
void IAMainWindow::onDirNames(const std::vector<std::string> & dirNames, void * data)
{
    IAMainWindow * mainWindow = static_cast<IAMainWindow *>(data);

    QTextCodec * codec = QTextCodec::codecForName("KOI8-R");
    std::vector<std::string>::const_iterator it;
    mainWindow->dirNames.clear();
    mainWindow->dirIndex.clear();
    int i = 0;
    for (it = dirNames.begin(); it != dirNames.end(); ++it) {
        if (*it != "") {
            QByteArray dir(it->c_str());
            QString dirName(codec->toUnicode(dir));
            mainWindow->dirNames.append(dirName);
            mainWindow->dirIndex[dirName] = i;
        }
        ++i;
    }
    mainWindow->updateDirNames();
}

// Menu actions
void IAMainWindow::createActions()
{
    connectAction = new QAction(onlineIcon, tr("Connect"), this);
    connect(connectAction, SIGNAL(triggered()), this, SLOT(onConnect()));

    disconnectAction = new QAction(offlineIcon, tr("Disconnect"), this);
    disconnectAction->setEnabled(false);
    connect(disconnectAction, SIGNAL(triggered()), this, SLOT(onDisconnect()));

    settingsAction = new QAction(QIcon(":/images/ia_settings.png"), tr("Settings"), this);
    connect(settingsAction, SIGNAL(triggered()), this, SLOT(onSettings()));

    hideAction = new QAction(QIcon(":/images/ia_hide.png"), tr("Hide"), this);
    connect(hideAction, SIGNAL(triggered()), this, SLOT(onHide()));

    showAction = new QAction(QIcon(":/images/ia_show.png"), tr("Show"), this);
    showAction->setEnabled(false);
    connect(showAction, SIGNAL(triggered()), this, SLOT(onShow()));

    quitAction = new QAction(QIcon(":/images/ia_quit.png"), tr("Quit"), this);
    connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
}

// Tray icon creation
void IAMainWindow::createTray()
{
    QMenu * trayMenu = new QMenu(this);
    trayMenu->addAction(connectAction);
    trayMenu->addAction(disconnectAction);
    trayMenu->addAction(settingsAction);
    trayMenu->addAction(hideAction);
    trayMenu->addAction(showAction);
    trayMenu->addAction(quitAction);

    sysTrayIcon = new IATrayIcon(onlineIcon, offlineIcon, this);
    sysTrayIcon->setContextMenu(trayMenu);
    sysTrayIcon->show();

    connect(sysTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), this, SLOT(onTrayActivated(QSystemTrayIcon::ActivationReason)));
}

void IAMainWindow::closeEvent(QCloseEvent * event)
{
    if (settings.value("minimize_on_close", "1").toBool() && sysTrayIcon != NULL) {
        onHide();
	if (firstTime) {
            QMessageBox::information(this, tr("InetAccess"), tr("The program will keep running in the "
	                                                        "system tray. To terminate the program, "
	                                                        "choose <b>Quit</b> in the context menu "
		                                                "of the system tray entry."));
	    firstTime = false;
	}
        event->ignore();
    } else {
        event->accept();
    }
}

// Hide window
void IAMainWindow::onHide()
{
    hideAction->setEnabled(false);
    hide();
    showAction->setEnabled(true);
}

// Show window
void IAMainWindow::onShow()
{
    showAction->setEnabled(false);
    show();
    hideAction->setEnabled(true);
}

// Tray events
void IAMainWindow::onTrayActivated(QSystemTrayIcon::ActivationReason reason)
{
    switch (reason) {
        case QSystemTrayIcon::Trigger:
        case QSystemTrayIcon::DoubleClick:
            if (isHidden()) {
                onShow();
            } else {
                onHide();
            }
            break;
        case QSystemTrayIcon::MiddleClick:
            break;
        default:
            ;
    }

}

void IAMainWindow::displayMessage(const QString & message)
{
    emit incomingMessage(message);
}

void IAMainWindow::setState(bool state)
{
    ind->setState(state);
    if (state) {
        connectAction->setEnabled(false);
        disconnectAction->setEnabled(true);
        if (!connectBtn->isChecked()) {
            // Newer to be here, just protection
            iaConnect();
        }
    } else {
        connectAction->setEnabled(true);
        disconnectAction->setEnabled(false);
        if (connectBtn->isChecked()) {
            // On timeout
            // TODO: change library to send errors on timeouts
            iaDisconnect();
        }
    }
    if (sysTrayIcon != NULL) {
	sysTrayIcon->setState(state);
    }
}
